import { z } from "zod";
import { tool } from "ai";
import { createScopedLogger } from "@/utils/logger";
import { createGenerateText } from "@/utils/llms";
import { getModel } from "@/utils/llms/model";
import { getUnifiedCalendarAvailability } from "@/utils/calendar/unified-availability";
import type { EmailAccountWithAI } from "@/utils/llms/types";
import type { EmailForLLM } from "@/utils/types";
import prisma from "@/utils/prisma";
import { getUserInfoPrompt } from "@/utils/ai/helpers";

const logger = createScopedLogger("calendar-availability");

const timeSlotSchema = z.object({
  start: z.string().describe("Start time in format YYYY-MM-DD HH:MM"),
  end: z
    .string()
    .describe(
      "End time in format YYYY-MM-DD HH:MM - infer meeting duration from email context",
    ),
});

const schema = z.object({
  suggestedTimes: z.array(timeSlotSchema),
  noAvailability: z
    .boolean()
    .optional()
    .describe(
      "Set to true if the user has no availability in the requested timeframe",
    ),
});

export type CalendarAvailabilityContext = z.infer<typeof schema>;

export async function aiGetCalendarAvailability({
  emailAccount,
  messages,
}: {
  emailAccount: EmailAccountWithAI;
  messages: EmailForLLM[];
}): Promise<CalendarAvailabilityContext | null> {
  if (!messages?.length) {
    logger.warn("No messages provided for calendar availability check");
    return null;
  }

  const threadContent = messages
    .map((msg, index) => {
      const content = `${msg.subject || ""} ${msg.content || ""}`.trim();
      return content ? `Message ${index + 1}: ${content}` : null;
    })
    .filter(Boolean)
    .join("\n\n");

  if (!threadContent) {
    logger.info("No content in thread messages, skipping calendar check");
    return null;
  }

  const calendarConnections = await prisma.calendarConnection.findMany({
    where: {
      emailAccountId: emailAccount.id,
      isConnected: true,
    },
    include: {
      calendars: {
        where: { isEnabled: true },
        select: {
          calendarId: true,
          timezone: true,
          primary: true,
        },
      },
    },
  });

  const userTimezone = getUserTimezone(emailAccount, calendarConnections);

  logger.trace("Determined user timezone", { userTimezone });

  const system = `You are an AI assistant that analyzes email threads to determine if they contain meeting or scheduling requests, and returns available meeting time slots.

TIMEZONE: All times (busy periods, suggested times) are in ${userTimezone}.

Your task is to:
1. Analyze if the email is scheduling-related (meeting, call, appointment)
2. Extract any date/time preferences from the email
3. Use checkCalendarAvailability to get busy periods (already in ${userTimezone})
4. Suggest ONLY times that DO NOT overlap with busy periods
5. Return time slots with start AND end times (infer duration from context: "quick call" = 30min, "meeting" = 60min)
6. If there are NO available times (user is busy all day), set noAvailability=true and return empty suggestedTimes array

CRITICAL: Do NOT suggest times overlapping with busy periods.
Example: If busy 2025-11-17 09:00 to 2025-11-17 17:00, suggest times AFTER 17:00 or BEFORE 09:00.
Example: If busy all day (00:00 to 23:59), return empty array and set noAvailability=true.

Format: "YYYY-MM-DD HH:MM"
If email mentions timezone (e.g., "5pm PST"), convert to ${userTimezone}.
Call "returnSuggestedTimes" only once.`;

  const prompt = `${getUserInfoPrompt({ emailAccount })}
  
<current_time>
${new Date().toISOString()}
</current_time>

<thread>
${threadContent}
</thread>`.trim();

  const modelOptions = getModel(emailAccount.user);

  const generateText = createGenerateText({
    emailAccount,
    label: "Calendar availability analysis",
    modelOptions,
  });

  let result: CalendarAvailabilityContext | null = null;

  await generateText({
    ...modelOptions,
    system,
    prompt,
    stopWhen: (result) =>
      result.steps.some((step) =>
        step.toolCalls?.some(
          (call) => call.toolName === "returnSuggestedTimes",
        ),
      ) || result.steps.length > 5,
    tools: {
      checkCalendarAvailability: tool({
        description:
          "Check calendar availability across all connected calendars (Google and Microsoft) for meeting requests",
        inputSchema: z.object({
          timeMin: z
            .string()
            .describe("The minimum time to check availability for"),
          timeMax: z
            .string()
            .describe("The maximum time to check availability for"),
        }),
        execute: async ({ timeMin, timeMax }) => {
          const startDate = new Date(timeMin);
          const endDate = new Date(timeMax);

          try {
            const busyPeriods = await getUnifiedCalendarAvailability({
              emailAccountId: emailAccount.id,
              startDate,
              endDate,
              timezone: userTimezone,
            });

            logger.trace("Unified calendar availability data", {
              busyPeriods,
            });

            return { busyPeriods };
          } catch (error) {
            logger.error("Error checking calendar availability", { error });
            return { busyPeriods: [] };
          }
        },
      }),
      returnSuggestedTimes: tool({
        description: "Return suggested times for a meeting",
        inputSchema: schema,
        execute: async (data) => {
          result = data;
        },
      }),
    },
  });

  return result;
}

function getUserTimezone(
  emailAccount: EmailAccountWithAI,
  calendarConnections: Array<{
    calendars: Array<{
      calendarId: string;
      timezone: string | null;
      primary: boolean;
    }>;
  }>,
): string {
  // First priority: user's explicitly set timezone
  if (emailAccount.timezone) {
    return emailAccount.timezone;
  }

  // Second: try to find the primary calendar's timezone
  for (const connection of calendarConnections) {
    const primaryCalendar = connection.calendars.find((cal) => cal.primary);
    if (primaryCalendar?.timezone) {
      return primaryCalendar.timezone;
    }
  }

  // Third: find any calendar with a timezone
  for (const connection of calendarConnections) {
    for (const calendar of connection.calendars) {
      if (calendar.timezone) {
        return calendar.timezone;
      }
    }
  }

  // Last resort: UTC
  return "UTC";
}
